/*
 * fs.c - Mokri
 * Contributors: xMine
 * (Any new Contributor may add himself/herself to the list)
 * Changes and fixes to be made: fopen - Warum darf path nicht freigegeben werden?
 * Source:
 */

#include "fs.h"

struct fs_entry *first_entry = NULL;
struct fs_entry *last_entry = NULL;
char working_path[1024]; // In diesem Ordner befinden wir uns gerade
uint32_t nodenum = 0;
struct fs_driver drivers[32];

struct fs_entry *fs_add_node (const char *filename, char flags, struct fs_entry* parent, char driver);

/* Initialisiert das Dateisystem
 */
void fs_init () {
	strcpy (working_path, "/"); // Aktuelles Arbeitsverzeichniss
	memset (drivers, 0x0, sizeof (struct fs_driver)*32);
}

/* Hilfsfunktion
 */
struct fs_path fs_path_helper (char *path) {
	int i;
	char *ptr = path;
	for (i=0; ptr[i] != 0x0; i++) {
		if (ptr[i] == '/') {
			struct fs_path ret;
			int j;
			for (j = i+1; ptr[j] != 0x0 && ptr[j] != (char)'/'; j++) { }
			ret.path = ptr + i + 1;
			ret.len = j - i - 1;

			if (ptr[j] == 0x0) {
				ret.last = TRUE;
			} else {
				ret.last = FALSE;
			}

			//printf ("fs_path_helper: i: %i j: %i\n", i, j);

			return ret;
		}
	}

	// Wenn wir hier landen sind wir bereits beim letzten Element angelangt
	struct fs_path ret;
	ret.path = path;
	ret.len = strlen(path);
	ret.last = TRUE;
	return ret;
}

/* Erstellt einen kompletten Pfad aus einem relativen Pfad
 * Parameter: Pfad
 * Rückgaben: Kompletter Pfad (wieder per free freigeben!!)
 */
char *fs_translate_path (const char *path) {
	char *ptr;

	if (path[0] == '/') { // absoluter Pfad?
		//printf ("fs_translate_path: No need to change anything.\n");
		size_t len = strlen(path);
		ptr = malloc (len+1);
		memset (ptr, 0x0, len+1);
		memcpy (ptr, path, (int)len);
		//strcpy (ptr, path);
	} else {
		//printf ("fs_translate_path: Need to add working dir.\n");
		ptr = malloc (strlen(path)+strlen(working_path)+1);
		strcpy (ptr, working_path);
		strcat (ptr, path);
	}

	int lastchar = strlen(ptr) - 1;
	if (lastchar > 0 && ptr[lastchar] == '/') {
		ptr[lastchar] = 0x0;
	}
	
	//printf ("fs_translate_path: '%s'\n", ptr);

	return ptr;
}

/* Sucht eine Datei/Ordner
 * Parameter: Name
 * Rückgaben: Bei Erfolg einen Pointer auf den Eintrag, ansonsten NULL
 */
struct fs_entry *fs_find_entry (const char *name) {
	if (first_entry == NULL) { // Gibts zumindest einen Eintrag?
		print ("fs_find_entry: No nodes!\n");
		return NULL;
	}

	char *path = fs_translate_path (name); // Ggf. relativen Pfad behandeln
	char *p = path;
	struct fs_entry *cur_parent = first_entry; // Aktuelles übergeordnetes Verzeichniss
	char cur[64];
	
	//printf ("fs_find_entry - strlen: %u\n", (uint32_t)strlen(path));
	
	if (strlen(path) == 1) {
		free (path);
		return first_entry;
	}
	
	// Hier wird nun die Ordnerstruktur durchgehangelt, bis der Eintrag gefunden wird
	
	while (TRUE) {
		struct fs_path help; // Hilfsstruct für fs_path_helper
		struct fs_entry *ptr = first_entry;
		help = fs_path_helper (p); // Aktuelles Verzeichniss auslesen
		
		memset (cur, 0x0, 64);
		memcpy (cur, help.path, help.len);
		while (ptr != NULL) {
			if ( ptr->parent == cur_parent && _strcmp (cur, ptr->name) == TRUE) {
				break;
			}
			ptr = ptr->next;
		}
		
		// Es gibt keinen Eintrag der passt
		if (ptr == NULL) {
			break;
		}
		
		// Ordner?
		if (ptr->flags & FS_FLAG_FOLDER) {
			cur_parent = ptr;
		}
		
		// Eintrag gefunden?
		if (help.last == TRUE) {
			free (path);
			return ptr;
		}
		
		p += help.len; // String Pointer erhöhen
	}
	
	free (path);
	return NULL;
}

/* Versucht eine Datei/Ordner zu finden
 * Parameter: Ziel
 * Rückgaben: TRUE wenn die Datei/Ordner exestiert
 */
BOOL fs_exists (const char *name) {
	if (fs_find_entry (name) != NULL) {
		return TRUE;
	}

	return FALSE;
}

/* Erstellt einen Dateiknoten
 * Parameter: Dateiname, Treiber ID
 * Rückgaben: Bei Erfolg 1, sonst Fehlernummer
 */
int fs_create_file (const char *filename, char driver) {
	if (fs_exists (filename)) {
		return FS_ERR_ALREADY_EXISTS;
	}
	
	int i = strlen(filename);
	for (; i >= 0 && filename[i] != '/'; i--); // Anfang des Namens finden
	i++;
	
	char Name[64], Path[1024]; // Name und Pfad trennen
	memset (Name, 0x0, 64);
	memset (Path, 0x0, 1024);
	memcpy (Name, filename+i, strlen(filename+i));
	memcpy (Path, filename, i);
	
	struct fs_entry *parent = fs_find_entry (Path);
	
	fs_add_node (Name, 0x0, parent, driver);
	
	return 1;
}

/* Erstellt einen Verzeichnissknoten
 * Parameter: Ordnername, Treiber ID
 * Rückgaben: Bei Erfolg 1, sonst Fehlernummer
 */
int fs_create_folder (const char *filename, char driver) {
	if (fs_exists (filename)) {
		return FS_ERR_ALREADY_EXISTS;
	}

	int i = strlen(filename)-1;
	for (; i >= 0 && filename[i] != '/'; i--); // Anfang des Namens finden
	i++;

	char Name[64], Path[1024]; // Name und Pfad trennen
	memset (Name, 0x0, 64);
	memset (Path, 0x0, 1024);
	memcpy (Name, filename+i, strlen(filename+i));
	memcpy (Path, filename, i);

	struct fs_entry *parent = fs_find_entry (Path);

	fs_add_node (Name, FS_FLAG_FOLDER, parent, driver);

	return 1;
}

/* Fügt einen Knoten in die Liste ein
 * Parameter: Name des Knoten, Flags, Elternknoten, Treiber
 * Rückgaben: Pointer auf den Knoten
 */
struct fs_entry *fs_add_node (const char *filename, char flags, struct fs_entry* parent, char driver) {
	struct fs_entry *entry = malloc (sizeof (struct fs_entry));

#if 0
	printf ("fs_add_node: %s Ordner: ", filename);
	if ( (flags & FS_FLAG_FOLDER) == FS_FLAG_FOLDER) {
		print ("JA ");
	} else {
		print ("NEIN ");
	}

	printf ("Treiber: %i\n", (int)driver);
#endif

	if (parent == NULL) {
		parent = first_entry; // Der Erste Eintrag ist das Root Verzeichniss
	}

	memcpy (entry->name, filename, 64);
	entry->flags = flags;
	entry->parent = parent;
	entry->driver = driver;
	entry->id = nodenum;
	
	if (flags & FS_FLAG_FOLDER) {
		parent->children++;
	}
	
	nodenum++;

	if (last_entry == NULL) {
		last_entry = entry;
		entry->next = NULL;
	} else {
		last_entry->next = entry;
		last_entry = entry;
	}

	if (first_entry == NULL) {
		first_entry = entry;
		entry->next = NULL;
	}

	return entry;
}

/* Trägt einen Treiber in die Liste ein
 * Parameter: Treiber Handler Funktionen (Sollte selbsterklärend sein)
 * Rückhgaben: Treiber ID, bei Fehler 0xFF
 */
char fs_announce_driver (
		BOOL (*h_init)(char),
		FILE *(*h_open)(const char*, struct fs_entry*, const char*),
		int (*h_close)(FILE*),
		size_t (*h_read)(FILE*, char*, size_t),
		size_t (*h_write)(FILE*, void*, size_t),
		int (*h_seek)(FILE *, int, int),
		size_t (*h_tell)(FILE *),
		BOOL (*h_remove)(const char*),
		BOOL (*h_create)(const char*, char),
		handle_t (*h_list_start)(const char*),
		BOOL (*h_list_end)(handle_t),
		struct fs_entry*(*h_list)(handle_t),
		BOOL (*h_copy)(const char*, const char*),
		BOOL (*h_move)(const char*, const char*),
		BOOL (*h_deinit)()
		) {
	int i;
	for (i=0; i<32; i++) {
		if (drivers[i].handler_init == NULL) {
			drivers[i].handler_init = h_init;
			drivers[i].handler_open = h_open;
			drivers[i].handler_close = h_close;
			drivers[i].handler_read = h_read;
			drivers[i].handler_write = h_write;
			drivers[i].handler_seek = h_seek;
			drivers[i].handler_tell = h_tell;
			drivers[i].handler_remove = h_remove;
			drivers[i].handler_create = h_create;
			drivers[i].handler_list_start = h_list_start;
			drivers[i].handler_list_end = h_list_end;
			drivers[i].handler_list = h_list;
			drivers[i].handler_copy = h_copy;
			drivers[i].handler_move = h_move;
			drivers[i].handler_deinit = h_deinit;

			if (h_init (i)) {
				return (char)i;
			} else {
				drivers[i].handler_init = NULL;
				return (char)0xFF;
			}
		}
	}

	return (char)0xFF;
}

/* Setzt das aktuelle Arbeitsverzeichniss
 * Parameter: Neues Arbeitsverzeichniss
 * Rückgaben: Keine
 */
void fs_set_working_dir (const char *newdir) {
	struct fs_entry *ptr = fs_find_entry (newdir);

	// Exestiert dieser Eintrag und ist es ein Ordner?
	if (ptr == NULL || !(ptr->flags & FS_FLAG_FOLDER)) {
		return;
	}

	char *dir = fs_translate_path (newdir);
	memcpy (working_path, dir, strlen(dir));
	working_path[strlen(dir)] = 0x0;
	free (dir);
}

/* Gibt das aktuelle Arbeitsverzeichniss zurück
 * Parameter: Zeiger auf einen Puffer, Länge des Puffers
 * Rückgaben: Bei Erfolg Zeiger auf buf, ansonsten NULL
 */
char *fs_get_working_dir (char *buf, uint32_t len) {
	if (buf == NULL) {
		buf = malloc (strlen(working_path)+1);
	}
	
	if (len < strlen(working_path)+1) {
		return NULL;
	}
	
	memcpy (buf, working_path, strlen(working_path)+1);
	return buf;
}

/* Parst die Dateiparameter, die an die fopen Funktion übergeben werden
 * Parameter: Parameter
 * Rückgaben: Bitmaske der Parameter
 */
char fs_parse_params (const char *params) {
	char ret = 0x0;
	int i, l = strlen(params);
	
	for (i=0; i<l; i++) {
		switch (params[i]) {
			case 'r':
				ret |= FS_ACC_READ;
				break;
			case 'w':
				ret |= FS_ACC_WRITE;
				break;
			case 'a':
				ret |= FS_ACC_APPEND;
				break;
			default: // Unbekannte einfach ignorieren
				break;
		}
	}
	
	return ret;
}

/* Gibt den ersten Eintrag im Dateisystem zurück (Normalerweise "/")
 * Parameter: Keine
 * Rückgaben: Zeiger auf den ersten Eintrag
 */
struct fs_entry *fs_get_first_entry () {
	return first_entry;
}

/** Wrapper Funktionen - Die f... Funktionen **/

/* Öffnet eine Datei
 * Parameter: Dateiname, flags
 * Rückgaben: Bei Erfolg pointer auf eine FILE Struktur, ansonsten NULL
 */
FILE *fopen (const char *filename, const char *flags) {
	struct fs_entry *eintrag = fs_find_entry (filename);

	// Exestiert der Eintrag und ist es eine Datei?
	if (eintrag == NULL || eintrag->flags & FS_FLAG_FOLDER) {
		return NULL;
	}

	// FILE *handler_open (const char *path, struct fs_entry *entry, const char *params);
	if (drivers[(int)eintrag->driver].handler_open == NULL) {
		return NULL;
	}
	
	//printf ("homedir = %s\n", working_path);

	char *path = fs_translate_path (filename);
	FILE *ptr = drivers[(int)eintrag->driver].handler_open (path, eintrag, flags);

	free (path);

	return ptr;
}

/* Schliesst einen Dateihandle
 * Parameter: Dateihandle
 * Rückgaben: Bei Erfolg ungleich FS_ERR_EOF
 */
int fclose (FILE *handle) {
	if (handle == NULL) {
		return FS_ERR_NULL;
	}

	if (drivers[(int)handle->driver].handler_close == NULL) {
		return FS_ERR_EOF;
	}

	return drivers[(int)handle->driver].handler_close (handle);
}

/* Liest aus einem Dateihandle
 * Parameter: Ausgabepuffer, Elementengröße, Element anzahl, Dateihandle
 * Rückgaben: Anzahl gelesener Bytes
 */
size_t fread (void *buffer, size_t size, size_t elements, FILE *handle) {
	if (handle == NULL) {
		return FS_ERR_NULL;
	}
	
	if ( (handle->access & FS_ACC_READ) != FS_ACC_READ ) {
		return FS_ERR_NO_PERMISSION;
	}

	if (drivers[(int)handle->driver].handler_read == NULL) {
		return FS_ERR_EOF;
	}

	return drivers[(int)handle->driver].handler_read (handle, (char*)buffer, size*elements);
}

/* Schreibt in einem Dateihandle
 * Parameter: Eingabepuffer, Elementgröße, Element anzahl, Dateihandle
 * Rückgaben: Geschriebene Bytes
 */
size_t fwrite (void *buffer, size_t size, size_t elements, FILE *handle) {
	if (handle == NULL) {
		return FS_ERR_NULL;
	}
	
	if ( (handle->access & FS_ACC_WRITE) != FS_ACC_WRITE ) {
		return FS_ERR_NO_PERMISSION;
	}

	if (drivers[(int)handle->driver].handler_write == NULL) {
		return FS_ERR_EOF;
	}

	return drivers[(int)handle->driver].handler_write (handle, buffer, size*elements);
}

/* Setzt die aktuelle Position in einem Dateihandle
 * Parameter: Dateihandle, Offset, Herkunft
 * Rückgaben: Bei Erfolg 0, ansonsten ungleich 0
 */
int fseek (FILE *handle, int offset, int origin) {
	if (handle ==  NULL) {
		return FS_ERR_NULL;
	}
	
	if (drivers[(int)handle->driver].handler_seek == NULL) {
		return FS_ERR_EOF;
	}
	
	return drivers[(int)handle->driver].handler_seek (handle, offset, origin);
}

/* Gibt die aktuelle Position in einer Datei zurück
 * Parameter: Dateihandle
 * Rückgaben: Position in der Datei
 */
int ftell (FILE *handle) {
	if (handle == NULL) {
		return FS_ERR_NULL;
	}
	
	if (drivers[(int)handle->driver].handler_tell == NULL) {
		return FS_ERR_EOF;
	}
	
	return drivers[(int)handle->driver].handler_tell (handle);
}

/** opendir, readdir, closedir, rewinddir **/

/* Öffnet ein Verzeichniss um sein Inhalt auszulesen
 * Parameter: Pfad zum Verzeichniss
 * Rückgaben: Bei Erfolg einen Zeiger auf eine DIR Struktur, ansonsten NULL
 */
DIR *opendir (const char *dirpath) {
	struct fs_entry *dir = fs_find_entry (dirpath);
	
	if (dir == NULL || !(dir->flags & FS_FLAG_FOLDER) ) { // Der Knoten exestiert nicht oder er ist kein Ordner
		#if 0
		if (!(dir->flags & FS_FLAG_FOLDER)) {
			print ("opendir: Kein Ordner\n");
		} else {
			print ("opendir: Nicht gefunden\n");
		}
		#endif
		
		free (dir);
		return NULL;
	}
	
	DIR *ret = (DIR*) malloc (sizeof(DIR));
	ret->parent = dir;
	ret->cur = NULL;
	ret->magic = DIR_MAGIC;
	ret->ptr = malloc (sizeof(dirent));
	memset (ret->ptr, 0x0, sizeof(dirent));
	ret->ptr->d_name = NULL;
	
	return ret;
}

/* Liest den nächsten Eintrag aus einem Verzeichniss aus
 * Parameter: Zeiger auf eine DIR Struktur
 * Rückgaben: Zeiger auf eine dirent Struktur
 */
dirent *readdir (DIR *dir) {
	if (dir == NULL) {
		return NULL;
	}
	
	if (dir->magic != DIR_MAGIC) {
		return NULL;
	}
	
	struct fs_entry *ptr = dir->cur;
	struct fs_entry *parent = dir->parent;
	
	if (ptr == NULL) {
		ptr = first_entry;
	} else {
		ptr = ptr->next;
	}
	
	while (ptr) {
		if (ptr->parent == parent) {
			break;
		}
		
		ptr = ptr->next;
	}
	
	if (ptr == NULL) {
		return NULL;
	}
	
	// Daten in die dirent Struktur eintragen
	uint16_t len = (uint16_t)strlen (ptr->name);
	dirent *ent = dir->ptr;
	ent->d_ino = (long)ptr->userdata;
	ent->d_reclen = (uint16_t)sizeof (dirent);
	ent->d_namlen = len;

	if (ent->d_name != NULL) {
		free (ent->d_name);
	}
	
	ent->d_name = (char*)malloc (len+1);
	memset (ent->d_name, 0x0, len+1);
	memcpy (ent->d_name, ptr->name, len);
	
	dir->cur = ptr;
	
	return ent;
}

/* Setzt die Position im Verzeichniss zurück
 * Parameter: Zeiger auf eine DIR Struktur
 * Rückgaben; Keine
 */
void rewinddir (DIR *dir) {
	if (dir == NULL) {
		return;
	}
	
	if (dir->magic != DIR_MAGIC) {
		return;
	}
	
	dir->cur = NULL;
}

/* Schliesst einen Ordnerhandle
 * Parameter: Zeiger auf eine DIR Struktur
 * Rückgaben: Bei Erfolg 0, ansonsten ungleich 0
 */
int closedir (DIR *dir) {
	if (dir == NULL) {
		return -1;
	}
	
	if (dir->magic != DIR_MAGIC) {
		return -1;
	}
	
	free (dir->ptr);// dirent Struktur freigeben
	free (dir);	// und die DIR Struktur ausm Speicher werfen
	
	return 0;
}